import Solve from './solve'
import type { UnionFunc } from './solve'

class Newton extends Solve {
  constructor(originalFunc: UnionFunc) {
    super(originalFunc)
  }
  unarySolve(target: number, initialGuess: number) {
    if (!this.originalUnaryFunc) {
      throw new Error('Not a unary function')
    }
    const fn = this.originalUnaryFunc
    const g = (x: number) => fn(x) - target
    const derivative = (x: number) =>
      (fn(x + Solve.TOLERANCE) - fn(x - Solve.TOLERANCE)) /
      (2 * Solve.TOLERANCE)
    let x = initialGuess
    for (let i = 0; i < Solve.MAX_ITERATIONS; i++) {
      const gx = g(x)
      if (Math.abs(gx) < Solve.TOLERANCE) {
        return x
      }
      const dgx = derivative(x)
      if (Math.abs(dgx) < Solve.EPSILON) {
        throw new Error('Zero derivative encountered')
      }
      x -= gx / dgx
    }
    throw new Error('Failed to converge after ${maxIterations} iterations')
  }
  binarySolve(
    target: number,
    initialGuess: number,
    knownVal: number,
    unknownPosition: 1 | 2,
  ) {
    if (!this.originalBinaryFunc) {
      throw new Error('Not a binary function')
    }
    const fn = this.originalBinaryFunc
    for (let i = 0; i < Solve.MAX_ITERATIONS; i++) {
      const fy =
        unknownPosition === 1
          ? fn(initialGuess, knownVal) - target
          : fn(knownVal, initialGuess) - target
      if (Math.abs(fy) < Solve.TOLERANCE) {
        return initialGuess
      }
      const dfy =
        unknownPosition === 1
          ? (fn(initialGuess + Solve.TOLERANCE, knownVal) -
              fn(initialGuess - Solve.TOLERANCE, knownVal)) /
            (2 * Solve.TOLERANCE)
          : (fn(knownVal, initialGuess + Solve.TOLERANCE) -
              fn(knownVal, initialGuess - Solve.TOLERANCE)) /
            (2 * Solve.TOLERANCE)
      if (Math.abs(dfy) < Solve.EPSILON) {
        throw new Error('Zero derivative encountered')
      }
      initialGuess -= fy / dfy
    }
    throw new Error('Failed to converge after ${maxIterations} iterations')
  }
}

export default Newton
